### Adapted from code in Artificial Intelligence: A Modern Approach, by
### Russell and Norvig

### Note to students: please do not use the AIMA code for HW 2. Use this
### code instead. I've made some changes to the AIMA code to make it simpler
### and easier to work with. Using the AIMA code directly will just give you
#### headaches.

import random

### global variable holding all possible agent actions
agent_actions = ['Left', 'Right', 'Suck', 'Up', 'Down']

### you should not need to change this code.
class Agent :
    """An Agent is a subclass of Object with one required slot,
    .program, which should hold a function that takes one argument, the
    percept, and returns an action. (What counts as a percept or action
    will depend on the specific environment in which the agent exists.) """

    def __init__(self):
        self.alive = True

    def program(percept):
        return raw_input('Percept=%s; action? ' % percept)


### a simple but stupid agent. Use this as a template when creating your own agents.
class RandomAgent(Agent):
    "An agent that chooses an action at random, ignoring all percepts."
    def __init__(self, actions):
        Agent.__init__(self)
        self.actions = actions

    def program(self, percept) :
        action = random.choice(self.actions)
        print "Percept: %s Action: %s" % (percept, action)
        return action


### A slightly smarter agent - basically a reflex agent, this program just uses
### its current percepts to decide on an action.
class TableDrivenAgent(Agent) :

    def __init__(self) :
        Agent.__init__(self)

        ### this is how our agent will determine what to do.
        ### for every possible percept, we will look up in our
        ### table (a dictionary) to find the correct action.

        ### this will only work in 3x3 grids.

        ### if the room is clean, we need to move
        self.actionTable = {((0,0), 'Clean') : 'Right',
                            ((1,0), 'Clean') : 'Right',
                            ((2,0), 'Clean') : 'Up',
                            ((2,1), 'Clean') : 'Up',
                            ((2,2), 'Clean') : 'Left',
                            ((1,2), 'Clean') : 'Left',
                            ((0,2), 'Clean') : 'Down',
                            ((0,1), 'Clean') : 'Down',
                            ((1,1), 'Clean') : 'Down'}
        ### if the room is dirty, let's clean it.
        for i in (0,1,2) :
            for j in (0,1,2) :
                self.actionTable[((i,j), 'Dirty')] = 'Suck'
        ### this gives us a dictionary that maps room coordinates and state to an action.


    def program(self, percept) :
        ### All we do is use the current percept to look up the correct
        ### action in a table.
        print "Percept: %s Action: %s" % (percept, self.actionTable[percept])
        return self.actionTable[percept]

    def printPerformance(self) :
        print "Agent: Performance is %s" % (self.performance)


### Smart agent
class ModelBasedAgent(Agent) :

    def __init__(self) :
        Agent.__init__(self)
        self.mem = []                   #list of all locations we have been to
        self.command = 's'              #the last command sent
        self.prev_percept = [[0,0], 's']#the previous percept received
        self.doneFlag = 0               #this flag is set if we have gone to a corner
        self.grid = [[0, 0], 0, 0]      #[[x,y], wallXflag, wallYflag]

    def program(self, percept) :
        #we first go to the known corner
        #once done, doneFlag will be set
        if not(self.doneFlag):
            cmd = self.goto_corner(percept)
            if(cmd == 'Done'):
                self.doneFlag = 1
                self.command = 'Up'
                print 'init finished, commencing spiralling'
            else:
                return cmd

        # TODO: we can also use the wallx and y flags
        if self.doneFlag: #start spiralling to the middle!
            # clean if dirty
            if percept[1] == 'Dirty' :
                return 'Suck'

            # if done, return Noop to let Agent do nothing
            if self.check_finish_with_list():
                return 'Noop'

            # if we have been there, do avoid maneuver rotate 90%
            if self.been_there(percept, self.mem, self.command):
                self.command = self.rotate(self.command)

            # if we hit a wall, remember that wall, then also rotate
            if self.prev_percept == percept:
                self.wallfoundUpdate()
                self.command = self.rotate(self.command)
            else: #only update the list and grid if percept is new
                self.update_list(percept, self.mem)
                self.update_grid(percept)

        self.prev_percept = percept
        return self.command




    ############################################
    ### ModelBasedAgent's specific functions ###
    ############################################

    #function that tries to go to a "known" corner
    #todo, build a flag for the mem
    #if mem flags are set, this function will find the closest corner
    def goto_corner(self, percept):
        if not(percept[0][1] == 0):
            return 'Down'

        if not(percept[0][0] == 0):
            return 'Left'

        return 'Done'

    #update the list if we haven't been there
    def update_list(self, percept, mem) :
        found = self.find_in_mem(percept[0], mem)
        if not found:
            mem.append(percept[0])

    ###rotate the agent 90% clockwise
    def rotate(self, command):
        if command == 'Up':
            return 'Right'
        if command == 'Down':
            return 'Left'
        if command == 'Right':
            return 'Down'
        if command == 'Left':
            return 'Up'

    # calculate next step location
    # check if we have been there
    # return 1 if yes, 0 otherwise
    def been_there(self, percept, mem, command):
        # calculate the next location for each command
        if command == 'Up':
            new_loc = (percept[0][0]    , percept[0][1]+1)
        if command == 'Down':
            new_loc = (percept[0][0]    , percept[0][1]-1)
        if command == 'Right':
            new_loc = (percept[0][0]+1  , percept[0][1])
        if command == 'Left':
            new_loc = (percept[0][0]-1  , percept[0][1])

        #return true if we hit known walls
        if (new_loc[0] < 0) or (new_loc[1] < 0):
            return 1

        # if the next location is found in the list, skip it
        if self.find_in_mem(new_loc, mem):
            return 1
        return 0

    # update if walls are found
    def wallfoundUpdate(self):
        #TODO: can also do this by checking the last command sent
        #if 'Up', we have found the northern wall
        #if 'Right', we have found the eastern
        if self.grid[1] == 0:
            self.grid[1] = 1 ### set flag: northern unknown wall found
        elif self.grid[2] == 0:
            self.grid[2] = 1 ### set flag: eastern unknown wall found

    # find xy value in a list, returns 1 if found, otherwise 0
    def find_in_mem(self, loc, mem):
        for x in range(len(mem)):
            if mem[x] == loc:
                return 1
        return 0

    # check if we are finished
    def check_finish_with_list(self) :
        if (self.grid[1] == 0) or (self.grid[2] == 0):
            return 0 ###return if grid not complete

        if (len(self.mem)+1) == ((self.grid[0][0]+1)*(self.grid[0][1]+1)) :
            return 1
        else:
            return 0

    # Check if we have been out of new bounds, update if so
    def update_grid(self, percept) :
        #if new X percept is greater than last one, update gridSize
        if percept[0][0] > self.grid[0][0] :
            self.grid[0][0] = percept[0][0]

        #if new Y percept is greater than last one, update gridSize
        if percept[0][1] > self.grid[0][1] :
            self.grid[0][1] = percept[0][1]


    #prints the performance of the agent
    def printPerformance(self) :
        print "Agent: Performance is %s" % (self.performance)

    #debug
    def printMem(self) :
        pass

    #debug
    def printGridSize(self) :
        print "Found GridSize was:", self.grid
